#include <stdlib.h>
#include "dh_net.h"
#include "base.h"
#include "msocket.h"
#include "log.h"

#define socket_errstr strerror(socket_geterror())

/// @brief 初始化tcp套接字
/// @param ip 网卡ip NULL=>监听所有
/// @param port 监听端口
/// @param backlog
/// @return
static inline socket_t __tcp_socket_init(const char *ip, int port, int backlog, int *errstep)
{
    int step = 0;
    int ret = socket_init();
    if (ret != 0)
    {
        log2_error(DH_LOG, "socket_init() fail:%s", socket_errstr);
        step = -5;
        goto fail0;
    }

    socket_t sock = socket_tcp();
    if (socket_invalid == sock) // 创建socket失败
    {
        log2_error(DH_LOG, "socket_tcp() fail:%s", socket_errstr);
        step = -4;
        goto fail0;
    }

    // 设置套接字端口可重用，修复了当Socket服务器重启时“地址已在使用(Address already in use)”的错误
    socket_setreuseaddr(sock, 1);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if (ip == NULL)
        addr.sin_addr.s_addr = INADDR_ANY;
    else
    {
        /* 将点分十进制的ip地址转为32位整型传入结构体 */
        int s = idh_net_pton(AF_INET, ip, &addr.sin_addr);
        if (s <= 0)
        {
            if (s == 0)
                log2_error(DH_LOG, "idh_net_pton() format error");
            else
                log2_error(DH_LOG, "idh_net_pton():%s", socket_errstr);

            step = -3;
            goto fail0;
        }
    }
    if (socket_bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == socket_error)
    {
        log2_error(DH_LOG, "socket_bind():%s", socket_errstr);
        step = -2;
        goto fail0;
    }

    if (socket_listen(sock, backlog) == socket_error)
    {
        log2_error(DH_LOG, "socket_listen():%s", socket_errstr);
        step = -1;
        goto fail0;
    }

fail0:
    if (step < 0 && step != -4)
    {
        socket_close(sock);
        sock = socket_invalid;
    }
    if (errstep != NULL)
        *errstep = step;
    return sock;
}

/// @brief 销毁tcp套接字
/// @param sock
static void __tcp_socket_deinit(socket_t sock)
{
    socket_close(sock);
    socket_cleanup();
}

/// @brief  初始化 tcp socket
/// @param errstep 异常步骤 -5：socket dll 初始化失败 -4：创建套接字失败 -3:idh_net_pton失败 -2:绑定失败 -1:监听失败 0:成功 1：不启用net模块
/// @return socket套接字 (失败/不启用:socket_invalid)
socket_t dh_net_init(int *errstep)
{
    char *net_on = config_get("net_on");
    if (net_on == NULL || strlen(net_on) == 0)
    {
        log2_warning(DH_LOG, "配置文件中设置了不启用net模块");
        if (errstep != NULL)
            *errstep = 1;
        return socket_invalid;
    }

    char *net_ip = config_get("net_ip");
    if (net_ip == NULL || strlen(net_ip) == 0)
        net_ip = DH_DEFAULT_IP;

    int port = DH_DEFAULT_PORT;
    char *net_port = config_get("net_port");
    if (net_port != NULL)
    {
        int port_len = strlen(net_port);
        if (port_len >= 4 && port_len <= 5)
        {
            port = atoi(net_port);
            port = port > 1024 && port < 65535 ? port : DH_DEFAULT_PORT;
        }
    }

    int backlog = DH_DEFAULT_BACKLOG;
    char *net_backlog = config_get("net_backlog");
    if (net_backlog != NULL && strlen(net_backlog) > 0)
    {
        int res = atoi(net_backlog);
        backlog = res > 0 ? res : backlog;
    }

    log2_debug(DH_LOG, "net:{on:%s ip:%s backlog:%d}", net_on, net_ip, backlog);
    return __tcp_socket_init(net_ip, port, backlog, errstep);
}

/// @brief 销毁 tcp sockeet
/// @param sock
void dh_net_deinit(socket_t sock)
{
    if (sock == socket_invalid)
        return;
    __tcp_socket_deinit(sock);
}

static void __dh_net_cli_socket_deinit(void *ptr)
{
    if (ptr == NULL)
        return;
    dh_net_cli_socket_t *cli_addr = (dh_net_cli_socket_t *)ptr;
    dh_net_str_t *item = cli_addr->slist;
    dh_net_str_t *next = item->next;
    while (item != NULL)
    {
        next = item->next;
        global_mpool_free(item->data);
        global_mpool_free(item);
        item = next;
    }
    global_mpool_free(cli_addr);
}

static dh_net_cli_socket_t *__dh_net_cli_socket_init(socket_t cli)
{
    dh_net_cli_socket_t *res = NULL;
    if (cli == socket_invalid)
        goto fail0;
    res = global_mpool_malloc(sizeof(dh_net_cli_socket_t));
    if (res == NULL)
        goto fail0;

    memcpy(res->sock, cli, sizeof(socket_t));
    res->slist = global_mpool_malloc(sizeof(dh_net_str_t));
    if (res->slist == NULL)
    {
        global_mpool_free(res);
        res = NULL;
        goto fail0;
    }
    res->slist->data = NULL;
    res->slist->len = 0;
    res->last = res->slist;
fail0:
    return res;
}

static void __dh_net_cli_socket_clear(dh_net_cli_socket_t *cli)
{
    if (cli == NULL)
        return;
    dh_net_str_t *item = cli->slist;
    dh_net_str_t *next = NULL;
    while (item != NULL)
    {
        next = item->next;
        if (item->data != NULL)
        {
            global_mpool_free(item->data);
            item->data = NULL;
        }
        item->len = 0;
        item->flag = 0;
        item = next;
    }
    cli->last = cli->slist;
}

static void __dh_net_cli_socket_append(dh_net_cli_socket_t *cli, uint8_t flag, const char *data)
{
    if (cli == NULL)
        return;
    size_t len = strlen(data);
    cli->last->flag = flag;
    cli->last->data = global_mpool_malloc(len + 1);
    if (cli->last->data == NULL)
        return;
    strncpy(cli->last->data, data, len);
    cli->last->len = len;
    // 下一个节点准备
    if (cli->last->next == NULL)
    {
        cli->last->next = global_mpool_malloc(sizeof(dh_net_str_t));
        if (cli->last->next == NULL)
            return;
    }
    cli->last = cli->last->next;
    cli->last->data = NULL;
    cli->last->len = 0;
}

/// @brief net 模块循环默认回调
/// @param env
/// @param userdata
/// @return
char dh_net_loop(socket_t sock, dh_net_recv_cb_impl cb, void *userdata)
{
    char ret = 0;
    if (sock == socket_invalid)
    {
        log2_error(DH_LOG, "socket 未被初始化成功");
        return -1;
    }

    fd_set fdset;
    array_t *fd_arr = array_init(__dh_net_cli_socket_deinit);
    if (fd_arr == NULL)
    {
        log2_error(DH_LOG, "内存不足 socket数组未被初始化成功");
        return -2;
    }
    array_add(fd_arr, &sock);

    dh_net_protocol_t buf = {0};

    dh_net_recv_event_t event = {
        .sock = sock,
        .cli_arr = fd_arr,
        .cli_idx = -1,
    };

    char *data = NULL;
    size_t data_len = 0;
    size_t data_cap = 0;

    int max_fd = 0;
    while (1)
    {
        FD_ZERO(&fdset);
        // 计算最大的文件描述符
        max_fd = 0;
        for (size_t i = 0; i < fd_arr->len; i++)
        {
            socket_t s = *(socket_t *)array_get(fd_arr, i);
            if (s == socket_invalid)
                continue;
            FD_SET(s, &fdset);
            if (s > max_fd)
                max_fd = s;
        }
        int select_ret = socket_select(max_fd + 1, &fdset, NULL, NULL, NULL);
        if (select_ret == 0)
        {
            log2_warning(DH_LOG, "socket_select() 超时");
            continue;
        }
        else if (select_ret < 0)
        {
            log2_error(DH_LOG, "socket_select() :%s", socket_errstr);
            ret = -3;
            break;
        }

        if (FD_ISSET(sock, &fdset)) // 服务器端收到数据
        {
            socket_t cli = socket_accept(socket, NULL, NULL);
            if (cli == socket_error)
            {
                log2_error(DH_LOG, "new cli socket_accept() :%s", socket_errstr);
            }
            else
            {
                int max_cli = DH_DEFAULT_MAXCLI;
                char *dh_net_maxcli = config_get("dh_net_maxcli");
                if (dh_net_maxcli != NULL && strlen(dh_net_maxcli) > 0)
                {
                    int atoi_ret = atoi(dh_net_maxcli);
                    max_cli = atoi_ret > 0 ? atoi_ret : max_cli;
                }
                if (max_cli < fd_arr->len)
                    log2_info(DH_LOG, "当前客户端数量(%d)超过上限(%d)", fd_arr->len, max_cli);

                dh_net_cli_socket_t *cli_addr = __dh_net_cli_socket_init(cli);
                if (cli_addr == NULL) // 无法分配内存
                {
                    log2_error(DH_LOG, "当前服务器内存不足，无法分配客户端句柄内存,请考虑重启 dh");
                    socket_close(cli);
                    continue;
                }

                int idx = -1;
                for (size_t i = 0; i < fd_arr->len; i++)
                {
                    dh_net_cli_socket_t *item = (dh_net_cli_socket_t *)array_get(fd_arr, i);
                    if ((item != NULL && item->sock != socket_invalid) || item == NULL)
                    {
                        array_update(fd_arr, i, cli_addr, 1);
                        idx = i;
                        break;
                    }
                }
                if (idx < 0) // 没位置了-> 强加扩容
                    array_add(fd_arr, cli_addr);
            }
        }
        else // 客户端收到消息
        {
            for (size_t i = 0; i < fd_arr->len; i++)
            {
                dh_net_cli_socket_t *cli_addr = (dh_net_cli_socket_t *)array_get(fd_arr, i);
                if (cli_addr == NULL || cli_addr->sock == socket_invalid || !FD_ISSET((cli_addr->sock), &fdset))
                    continue;
                // 数据接收
                memset(&buf, 0, sizeof(buf));
                int recv_ret = socket_recv(cli_addr->sock, &buf, sizeof(buf), MSG_WAITALL);
                if (recv_ret <= 0)
                {
                    log2_info(DH_LOG, "cli(%d) recv ret:%d fail:%s", cli_addr->sock, recv_ret, socket_errstr);
                    socket_close(cli_addr->sock);
                    array_update(fd_arr, i, NULL, 1);
                    continue;
                }

                __dh_net_cli_socket_append(cli_addr, buf.flag, buf.buf); // 数据追加
                if (bitmap_getbit(buf.flag, DH_NET_PROTOCOL_FLAG_END))   // 结束符
                {
                    if (cb != NULL)
                    {
                        ret = cb(&event, userdata);
                    }
                    __dh_net_cli_socket_clear(cli_addr); // 清除数据
                }
            }
        }
    }

    array_deinit(&fd_arr);
    return ret;
}